﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Eco.Handles;
using uCoreInterfaces2;
using System.IO;
using Core.Utility;
using segDroopyEyes.Eco.Services.XmlQueryService2;
using Descriptions_DomainModel.Model;
using Eco.Services;
using Eco.ObjectImplementation;
using Eco.ObjectRepresentation;
using Core.Logger;
using Descriptions_DomainModel.Model.Properties;

namespace DomainModelEditor
{
    /// <summary>
    /// Класс, реализующий загрузку и сохранение файлов DomainModel и библиотеки LPMED-функций
    /// </summary>
    public class DomainModelLoader
    {
        private IEcoServiceProvider ecoSpace;
        private IOclService oclService;

        /// <summary>
        /// ID, указывающая, что звено предназначено для работы в контексте DomainModel
        /// </summary>
        public const string DomainModelBelongingID = "DomainModel";
        /// <summary>
        /// Имя вспомогатательного звена, подлежащего удалению
        /// </summary>
        public const string FakeNodeName = "DomainModelFakeNode";

        /// <summary>
        /// Конструктор загрузчика описания оборудования
        /// </summary>
        /// <param name="ecoSpace">среда ECO</param>
        public DomainModelLoader(IEcoServiceProvider ecoSpace)
        {
            if (ecoSpace == null)
                throw new NullReferenceException("EcoSpace cannot be null!");

            this.ecoSpace = ecoSpace;

            oclService = ecoSpace.GetEcoService<IOclService>();
        }

        #region Public methods

        /// <summary>
        /// Загрузить DomainModel из файла
        /// </summary>
        /// <param name="fileName">Имя файла</param>
        public void LoadDomainModelFromFile(string fileName)
        {
            var dess = ecoSpace.GetEcoService<IXmlSerializerService>();

            //сейчас - открытие и ручками бегаем по объектам, удаляем лишние и налаживаем связи

            PathUtility.RestoreCurrentDirectory(Locality.ExeFile);

            #region Проверка корректности

            //путь до файла с узлами
            var nodespath = Path.ChangeExtension(
                Path.GetDirectoryName(Path.GetFullPath(fileName))
                + Path.DirectorySeparatorChar
                + Path.GetFileNameWithoutExtension(fileName), Settings.Default.NodesExtension);

            //путь до файла с библиотекой функций
            var funcpath = Path.GetDirectoryName(fileName) +
                Path.DirectorySeparatorChar +
                Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(fileName)) + "." +
                Settings.Default.FunctionLibraryExtension;

            if (!File.Exists(fileName))
            {
                throw new LoadingDomainModelException("Главный файл с описанием оборудования не найден: " + fileName);
            }

            if (!File.Exists(funcpath))
            {
                throw new LoadingDomainModelException("Отсутствует файл описания оборудования: " + funcpath);
            }

            if (!File.Exists(nodespath))
            {
                throw new LoadingDomainModelException("Отсутствует файл описания оборудования: " + nodespath);
            }

            #endregion

            PathUtility.RestoreCurrentDirectory(Locality.ExeFile);

            try
            {
                //десериализуем основной файл
                dess.LoadFromFile(fileName);

                //десериализуем файл с узлами
                dess.LoadFromFile(nodespath);

                //десериализуем файл с библиотекой функций
                dess.LoadFromFile(funcpath);
            }
            catch (Exception e)
            {
                throw new LoadingDomainModelException("Ошибка при чтении файла описания оборудования. " + e.Message);
            }

            //для всех групп узлов восстанавливаем связь self.Nodes
            ConnectNodesToGroup();
            ConnectNodes();
            ClearFakeNodes();
            RestoreStatePinBounds();
            ConnectFunctionLibrary();
            ConnectNodesAndFunctions();
        }

        /// <summary>
        /// Сохранить DomainModel в файл
        /// </summary>
        /// <param name="fileName">Имя файла</param>
        public void SaveDomainModelToFile(string fileName)
        {
            var sserv = ecoSpace.GetEcoService<IXmlQueryServiceSEG>();

            UpdateStatePinBounds();

            PathUtility.RestoreCurrentDirectory(Locality.ExeFile);

            Directory.CreateDirectory(Path.GetDirectoryName(fileName));

            //сохраняем DomainModel
            sserv.ExeXmlFile2XmlFile(null,
                Settings.Default.SaveDomainModelQuery,
                //"", Path.ChangeExtension(fileName, Settings.Default.DomainModelExtension));
                    "", fileName);

            //путь до файла с узлами
            var nodespath = Path.GetDirectoryName(fileName) +
                Path.DirectorySeparatorChar +
                Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(fileName)) + "." +
                Settings.Default.NodesExtension;

            PathUtility.RestoreCurrentDirectory(Locality.ExeFile);
            //сохраняем узлы (ибо дерево никак сериализовать вместе с остальными структурами)
            sserv.ExeXmlFile2XmlFile(null, Settings.Default.SaveNodesQuery, "", nodespath);

            //путь до файла с библиотекой функций
            var funcpath = Path.GetDirectoryName(fileName) +
                Path.DirectorySeparatorChar +
                Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(fileName)) + "." +
                Settings.Default.FunctionLibraryExtension;

            PathUtility.RestoreCurrentDirectory(Locality.ExeFile);
            //сохраняем библиотеку функций
            sserv.ExeXmlFile2XmlFile(null, Settings.Default.SaveFunctionLibraryQuery, "", funcpath);
        }
        
        #endregion

        #region Restoring connections

        /// <summary>
        /// Подготовить Node.StateSet для сериализации
        /// </summary>
        private void UpdateStatePinBounds()
        {
            var nodes = ecoSpace.GetEcoService<IOclService>().Evaluate("Node.allinstances").GetAsIList<Node>();

            foreach (var node in nodes)
            {
                node.StatePinMax = node.StateSet.UpperBound;
                node.StatePinMin = node.StateSet.BottomBound;
            }
        }

        /// <summary>
        /// Восстановить связь NodeGroup.Nodes
        /// </summary>
        private void ConnectNodesToGroup()
        {
            var allnodes = oclService.Evaluate("Node.allInstances").GetAsIList<Node>();
            var groups = oclService.Evaluate("DomainModel.allInstances->first.NodeGroups")
                .GetAsIList<NodeGroup>();

            foreach (var ngroup in groups)
            {
                //выбираем "левые" узлы
                var fakenodes = from nod in ngroup.Nodes
                                where (nod.Name == "DomainModelFakeNode") && (nod.BelongingID == DomainModelBelongingID)
                                select nod;

                foreach (var nod in fakenodes)
                {
                    //Ищем замену из нормальных узлов
                    var normal = allnodes.Find(n =>
                        (n.OldID == nod.OldID) && (n.Name != "DomainModelFakeNode") && (n.BelongingID == DomainModelBelongingID));

                    //добавляем нормальный узел
                    ngroup.Nodes.Add(normal);
                }

                //удаляем "левые" узлы
                //foreach (var fnod in ngroup.Nodes)
                var count = ngroup.Nodes.Count;
                for (int i = 0; i < count; i++)
                {
                    var fnod = ngroup.Nodes[0];
                    if ((fnod.Name == "DomainModelFakeNode") && (fnod.BelongingID == DomainModelBelongingID))
                    {
                        fnod.StateSet.AsIObject().Delete();
                        fnod.AsIObject().Delete();
                    }
                }
            }

        }

        /// <summary>
        /// Восстановить связь NodeGroup.Nodes
        /// </summary>
        private void ConnectNodes()
        {
            var allnodes = oclService.Evaluate("Node.allInstances").GetAsIList<Node>();

            //выбираем нормальные узлы
            var normalnodes = from nod in allnodes
                              where (nod.Name != "DomainModelFakeNode") && (nod.BelongingID == DomainModelBelongingID)
                              select nod;

            //для всех нормальных узлов мы восстанавливаем связь self.Nodes
            foreach (var node in normalnodes)
            {
                //для каждого узла мы ищем замену из нормальных узлов
                foreach (var nnod in node.Nodes)
                {
                    //Ищем замену из нормальных узлов
                    var normal = allnodes.Find(n =>
                        (n.OldID == nnod.OldID) && (n.Name != "DomainModelFakeNode") && (n.BelongingID == DomainModelBelongingID));

                    //добавляем нормальный узел
                    node.Nodes.Add(normal);
                }

                foreach (var fnod in node.Nodes)
                    if ((fnod.Name == "DomainModelFakeNode") && (fnod.BelongingID == DomainModelBelongingID))
                    {
                        fnod.StateSet.AsIObject().Delete();
                        fnod.AsIObject().Delete();
                    }
            }
        }

        /// <summary>
        /// Удалить все узлы с пустым именем
        /// </summary>
        private void ClearFakeNodes()
        {
            var allnodes = oclService.Evaluate("Node.allInstances").GetAsIList<Node>();

            var fakenodes = from nod in allnodes
                            where (nod.Name == "DomainModelFakeNode") && (!nod.AsIObject().Deleted) && (nod.BelongingID == DomainModelBelongingID)
                            select nod;

            int count = fakenodes.Count();
            for (int i = 0; i < count; i++)
            {
                fakenodes.ElementAt(0).Methods.Clear();
                fakenodes.ElementAt(0).NamedStates.Clear();
                if (fakenodes.ElementAt(0).StateSet != null)
                    fakenodes.ElementAt(0).StateSet.AsIObject().Delete();
                fakenodes.ElementAt(0).AsIObject().Delete();
            }
        }

        /// <summary>
        /// Вооставновить Node.StateSet
        /// </summary>
        private void RestoreStatePinBounds()
        {
            var nodes = ecoSpace.GetEcoService<IOclService>().Evaluate("Node.allinstances").GetAsIList<Node>();

            foreach (var node in nodes)
            {
                node.StateSet.UpperBound = node.StatePinMax;
                node.StateSet.BottomBound = node.StatePinMin;
            }
        }

        /// <summary>
        /// Восстанвоить связь DomainModel.FunctionLibrary
        /// </summary>
        private void ConnectFunctionLibrary()
        {
            var libs = oclService.Evaluate("FunctionLibrary.allInstances").GetAsIList<FunctionLibrary>();
            var dommod = oclService.Evaluate("DomainModel.allInstances->first").AsObject as DomainModel;

            var fakelib = dommod.FunctionLibrary;

            //TODO: исправить баг с ID библиотеки, которая сериализуется вместе с DomainModel
            //сейчас - т.к. мы очищаем EcoSpace, то у нас после десериализации остаются только 2 библиотеки (если файлы корректны, конечно)
            dommod.FunctionLibrary = libs.FirstOrDefault(lib =>
                (lib.OldID != string.Empty) && (lib != fakelib));

            fakelib.AsIObject().Delete();
        }

        /// <summary>
        /// Восстанвоить связи узлов и ф-ий Goto- и ShiftFunction
        /// </summary>
        private void ConnectNodesAndFunctions()
        {
            var nodes = oclService.Evaluate("Node.allInstances").GetAsIList<Node>();
            var functions = oclService
                .Evaluate("DomainModel.allInstances->first.FunctionLibrary.Functions")
                .GetAsIList<Function>();

            foreach (var nod in nodes)
            {
                //GotoFunction
                if (nod.GotoFunctionID != string.Empty)
                    nod.GotoFunction = functions.Find(func =>
                        (func.OldID == nod.GotoFunctionID) && (func.Name != string.Empty));

                //ShiftFunction
                if (nod.ShiftFunctionID != string.Empty)
                    nod.ShiftFunction = functions.Find(func =>
                        (func.OldID == nod.ShiftFunctionID) && (func.Name != string.Empty));
            }
        }
        
        #endregion

    }
}
